// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.

namespace Microsoft.Samples.Commerce.Order.CommerceEntities
{
    using Microsoft.Commerce.Contracts;

    /// <summary>
    /// Represents a line item of a basket.
    /// </summary>
    public class LineItem : ICommerceEntity
    {
        /// <summary>
        /// defines the model name for this commerce entity
        /// </summary>
        public const string ModelNameDefinition = "LineItem";

        /// <summary>
        /// The commerce entity.
        /// </summary>
        private CommerceEntity commerceEntity;

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="LineItem"/> class.
        /// </summary>
        public LineItem()
        {
            this.commerceEntity = new CommerceEntity(LineItem.ModelNameDefinition);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LineItem"/> class.
        /// </summary>
        /// <param name="commerceEntity">The commerce entity.</param>
        public LineItem(CommerceEntity commerceEntity)
        {
            this.commerceEntity = commerceEntity;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LineItem"/> class.
        /// </summary>
        /// <param name="catalogName">Name of the catalog.</param>
        /// <param name="productId">The product id.</param>
        /// <param name="quantity">The quantity.</param>
        public LineItem(string catalogName, string productId, decimal quantity) : this()
        {
            this.CatalogName = catalogName;
            this.ProductId = productId;
            this.Quantity = quantity;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LineItem"/> class.
        /// </summary>
        /// <param name="catalogName">Name of the catalog.</param>
        /// <param name="productId">The product id.</param>
        /// <param name="variantId">The variant id.</param>
        /// <param name="quantity">The quantity.</param>
        public LineItem(string catalogName, string productId, string variantId, decimal quantity)
            : this(catalogName, productId, quantity)
        {
            this.VariantId = variantId;
        }

        #endregion

        #region ICommerceEntity Members

        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        /// <value>The id of the entity.</value>
        public string Id
        {
            get { return this.commerceEntity.Id; }
            set { this.commerceEntity.Id = value; }
        }

        /// <summary>
        /// Gets or sets the type name of this object instance
        /// <remarks>Required so that the Builders can convert to the Multi-channel Commerce Foundation CommerceEntity base type</remarks>
        /// </summary>
        /// <value></value>
        public string ModelName
        {
            get { return this.commerceEntity.ModelName; }
            set { this.commerceEntity.ModelName = value; }
        }

        /// <summary>
        /// Gets or sets this objects collection of Properties and their corresponding values
        /// </summary>
        /// <value></value>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Collection setting is required")]
        public CommercePropertyCollection Properties
        {
            get { return this.commerceEntity.Properties; }
            set { this.commerceEntity.Properties = value; }
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the basket level discount amount for a line item.
        /// </summary>
        /// <value>The basket level discount amount.</value>
        /// <remarks>
        /// The discounted amount applied to the line item based on the basket level discounts.
        /// </remarks>
        public decimal? BasketLevelDiscountAmount
        {
            get { return (decimal?)this.commerceEntity.GetPropertyValue(PropertyName.BasketLevelDiscountAmount); }
            set { this.commerceEntity.SetPropertyValue(PropertyName.BasketLevelDiscountAmount, value ?? null); }
        }

        /// <summary>
        /// Gets or sets the collection of basket-level discounts applied for the current line item.
        /// </summary>
        /// <value>The basket level discounts.</value>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Collection setting is required")]
        public CommerceRelationshipList BasketLevelDiscounts
        {
            get { return this.commerceEntity.GetPropertyValue(RelationshipName.BasketLevelDiscounts) as CommerceRelationshipList; }
            set { this.commerceEntity.SetPropertyValue(RelationshipName.BasketLevelDiscounts, value); }
        }

        /// <summary>
        /// Gets or sets the name of the catalog the current line item is associated with.
        /// </summary>
        /// <value>The name of the catalog.</value>
        public string CatalogName
        {
            get { return this.commerceEntity.GetPropertyValue(PropertyName.CatalogName) as string; }
            set { this.commerceEntity.SetPropertyValue(PropertyName.CatalogName, value); }
        }

        /// <summary>
        /// Gets or sets the name of the base catalog the current line item is associated with.
        /// </summary>
        /// <value>The name of the catalog.</value>
        public string BaseCatalogName
        {
            get { return this.commerceEntity.GetPropertyValue(PropertyName.BaseCatalogName) as string; }
            set { this.commerceEntity.SetPropertyValue(PropertyName.BaseCatalogName, value); }
        }

        /// <summary>
        /// Gets or sets the display name of the line item.
        /// </summary>
        /// <value>The display name.</value>
        public string DisplayName
        {
            get { return this.commerceEntity.GetPropertyValue(PropertyName.DisplayName) as string; }
            set { this.commerceEntity.SetPropertyValue(PropertyName.DisplayName, value); }
        }

        /// <summary>
        /// Gets or sets if this item's Gift Message.
        /// </summary>
        /// <value><c>true</c> if [gift message]; otherwise, <c>false</c>.</value>
        public string GiftMessage
        {
            get { return (string)this.commerceEntity.GetPropertyValue(PropertyName.GiftMessage); }
            set { this.commerceEntity.SetPropertyValue(PropertyName.GiftMessage, value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether this item should be Gift Wrapped.
        /// </summary>
        /// <value><c>true</c> if [gift wrap]; otherwise, <c>false</c>.</value>
        public bool? GiftWrap
        {
            get { return (bool?)this.commerceEntity.GetPropertyValue(PropertyName.GiftWrap); }
            set { this.commerceEntity.SetPropertyValue(PropertyName.GiftWrap, value ?? null); }
        }

        /// <summary>
        /// Gets or sets the in-stock quantity for the current line item.
        /// </summary>
        /// <value>The in stock quantity.</value>
        public decimal? InStockQuantity
        {
            get { return (decimal?)this.commerceEntity.GetPropertyValue(PropertyName.InStockQuantity); }
            set { this.commerceEntity.SetPropertyValue(PropertyName.InStockQuantity, value ?? null); }
        }

        /// <summary>
        /// Gets or sets a value indicating the inventory condition.
        /// </summary>
        /// <value>The inventory condition.</value>
        public int? InventoryCondition
        {
            get { return (int?)this.commerceEntity.GetPropertyValue(PropertyName.InventoryCondition); }
            set { this.commerceEntity.SetPropertyValue(PropertyName.InventoryCondition, value ?? null); }
        }

        /// <summary>
        /// Gets or sets the sum of all line item-based discount amounts for a single line item.
        /// </summary>
        /// <value>The item level discount amount.</value>
        public decimal? ItemLevelDiscountAmount
        {
            get { return (decimal?)this.commerceEntity.GetPropertyValue(PropertyName.ItemLevelDiscountAmount); }
            set { this.commerceEntity.SetPropertyValue(PropertyName.ItemLevelDiscountAmount, value ?? null); }
        }

        /// <summary>
        /// Gets or sets a collection of line item-level discounts applied to the current line item.
        /// </summary>
        /// <value>The item level discounts.</value>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Collection setting is required")]
        public CommerceRelationshipList ItemLevelDiscounts
        {
            get { return this.commerceEntity.GetPropertyValue(RelationshipName.ItemLevelDiscounts) as CommerceRelationshipList; }
            set { this.commerceEntity.SetPropertyValue(RelationshipName.ItemLevelDiscounts, value); }
        }

        /// <summary>
        /// Gets or sets the Priority Code of this item. This is used mostly in ShoppingList or GiftRegistry to set a Priority of the items.
        /// </summary>
        /// <value>The item priority.</value>
        public int? ItemPriority
        {
            get { return (int?)this.commerceEntity.GetPropertyValue(PropertyName.ItemPriority); }
            set { this.commerceEntity.SetPropertyValue(PropertyName.ItemPriority, value ?? null); }
        }

        /// <summary>
        /// Gets or sets the current list price of the line item in the product catalog.
        /// </summary>
        /// <value>The list price.</value>
        public decimal? ListPrice
        {
            get { return (decimal?)this.commerceEntity.GetPropertyValue(PropertyName.ListPrice); }
            set { this.commerceEntity.SetPropertyValue(PropertyName.ListPrice, value ?? null); }
        }

        /// <summary>
        /// Gets or sets the unit price of the line item at the time it was added to the basket.
        /// </summary>
        /// <value>The placed price.</value>
        public decimal? PlacedPrice
        {
            get { return (decimal?)this.commerceEntity.GetPropertyValue(PropertyName.PlacedPrice); }
            set { this.commerceEntity.SetPropertyValue(PropertyName.PlacedPrice, value ?? null); }
        }

        /// <summary>
        /// Gets or sets the identifier of the product associated with the current line item.
        /// </summary>
        /// <value>The product id.</value>
        public string ProductId
        {
            get { return this.commerceEntity.GetPropertyValue(PropertyName.ProductId) as string; }
            set { this.commerceEntity.SetPropertyValue(PropertyName.ProductId, value); }
        }

        /// <summary>
        /// Gets or sets the number of units associated with the current line item.
        /// </summary>
        /// <value>The quantity.</value>
        public decimal? Quantity
        {
            get { return (decimal?)this.commerceEntity.GetPropertyValue(PropertyName.Quantity); }
            set { this.commerceEntity.SetPropertyValue(PropertyName.Quantity, value ?? null); }
        }

        /// <summary>
        /// Gets or sets the shipping address identifier associated with the current line item.
        /// </summary>
        /// <value>The shipping address id.</value>
        public string ShippingAddressId
        {
            get { return this.commerceEntity.GetPropertyValue(PropertyName.ShippingAddressId) as string; }
            set { this.commerceEntity.SetPropertyValue(PropertyName.ShippingAddressId, value); }
        }

        /// <summary>
        /// Gets or sets the shipping method identifier associated with the current line item.
        /// </summary>
        /// <value>The shipping method id.</value>
        public string ShippingMethodId
        {
            get { return this.commerceEntity.GetPropertyValue(PropertyName.ShippingMethodId) as string; }
            set { this.commerceEntity.SetPropertyValue(PropertyName.ShippingMethodId, value); }
        }

        /// <summary>
        /// Gets or sets the status of the current line item.
        /// </summary>
        /// <value>The status.</value>
        public string Status
        {
            get { return this.commerceEntity.GetPropertyValue(PropertyName.Status) as string; }
            set { this.commerceEntity.SetPropertyValue(PropertyName.Status, value); }
        }

        /// <summary>
        /// Gets or sets the total price of the current line item when it was added to the basket.
        /// </summary>
        /// <value>The total.</value>
        /// <remarks>
        /// Total is calculated as follows:
        /// Total = (ListPrice * Quantity) - LineItemDiscountAmount - BasketLevelDiscountAmount.
        /// </remarks>
        public decimal? Total
        {
            get { return (decimal?)this.commerceEntity.GetPropertyValue(PropertyName.Total); }
            set { this.commerceEntity.SetPropertyValue(PropertyName.Total, value ?? null); }
        }

        /// <summary>
        /// Gets or sets the product variant identifier for the current line item.
        /// </summary>
        /// <value>The variant id.</value>
        public string VariantId
        {
            get { return this.commerceEntity.GetPropertyValue(PropertyName.VariantId) as string; }
            set { this.commerceEntity.SetPropertyValue(PropertyName.VariantId, value); }
        }

        #endregion

        #region Implicit operators
        /// <summary>
        /// Toes the user profile.
        /// </summary>
        /// <param name="commerceEntity">The commerce entity.</param>
        /// <returns></returns>
        public static implicit operator LineItem(CommerceEntity commerceEntity)
        {
            System.Diagnostics.Debug.Assert(LineItem.ModelNameDefinition.Equals(commerceEntity.ModelName), "Unexpected model name");
            return new LineItem(commerceEntity);
        }
        #endregion

        /// <summary>
        /// Required so that the Builders can convert to the Multi-channel Commerce Foundation CommerceEntity base type using <see cref="ModelName"/>
        /// </summary>
        /// <returns>This instances base type</returns>
        public CommerceEntity ToCommerceEntity()
        {
            return this.commerceEntity;
        }

        #region Property key names

        /// <summary>
        /// Names of the properties exposed by this commerce entity.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Needed for external access"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1053:StaticHolderTypesShouldNotHaveConstructors", Justification = "Cannot make it private as it cannot be derived from")]
        public class PropertyName : CommerceEntity.PropertyName
        {
            #region Constants

            /// <summary>
            /// The name of the BasketLevelDiscountAmount property for a LineItem.
            /// </summary>
            public const string BasketLevelDiscountAmount = "BasketLevelDiscountAmount";

            /// <summary>
            /// The name of the CatalogName property for a LineItem.
            /// </summary>
            public const string CatalogName = "CatalogName";

            /// <summary>
            /// The name of the BaseCatalogName property for a LineItem.
            /// </summary>
            public const string BaseCatalogName = "BaseCatalogName";

            /// <summary>
            /// The name of the DisplayName property for a LineItem.
            /// </summary>
            public const string DisplayName = "DisplayName";

            /// <summary>
            /// The name of the GiftMessage property for a LineItem.
            /// </summary>
            public const string GiftMessage = "GiftMessage";

            /// <summary>
            /// The name of the GiftWrap property for a LineItem.
            /// </summary>
            public const string GiftWrap = "GiftWrap";

            /// <summary>
            /// The name of the InStockQuantity property for a LineItem.
            /// </summary>
            public const string InStockQuantity = "InStockQuantity";

            /// <summary>
            /// The name of the InventoryCondition property for a LineItem.
            /// </summary>
            public const string InventoryCondition = "InventoryCondition";

            /// <summary>
            /// The name of the ItemLevelDiscountAmount property for a LineItem.
            /// </summary>
            public const string ItemLevelDiscountAmount = "ItemLevelDiscountAmount";

            /// <summary>
            /// The name of the ItemPriority property for a LineItem.
            /// </summary>
            public const string ItemPriority = "ItemPriority";

            /// <summary>
            /// The name of the ListPrice property for a LineItem.
            /// </summary>
            public const string ListPrice = "ListPrice";

            /// <summary>
            /// The name of the PlacedPrice property for a LineItem.
            /// </summary>
            public const string PlacedPrice = "PlacedPrice";

            /// <summary>
            /// The name of the ProductId property for a LineItem.
            /// </summary>
            public const string ProductId = "ProductId";

            /// <summary>
            /// The name of the Quantity property for a LineItem.
            /// </summary>
            public const string Quantity = "Quantity";

            /// <summary>
            /// The name of the ShippingAddressId property for a LineItem.
            /// </summary>
            public const string ShippingAddressId = "ShippingAddressId";

            /// <summary>
            /// The name of the ShippingMethodId property for a LineItem.
            /// </summary>
            public const string ShippingMethodId = "ShippingMethodId";

            /// <summary>
            /// The name of the Status property for a LineItem.
            /// </summary>
            public const string Status = "Status";

            /// <summary>
            /// The name of the Total property for a LineItem.
            /// </summary>
            public const string Total = "Total";

            /// <summary>
            /// The name of the VariantId property for a LineItem.
            /// </summary>
            public const string VariantId = "VariantId";

            #endregion
        }

        /// <summary>
        /// Class defining Properties of Items that may be related to a LineItem
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", Justification = "Required because of API specification (i.e. DataContract design)")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Needed for external access"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1053:StaticHolderTypesShouldNotHaveConstructors", Justification = "Cannot make it private as it cannot be derived from")]
        public class RelationshipName
        {
            #region Constants

            /// <summary>
            /// The name of the BasketLevelDiscounts property for an item related to a LineItem.
            /// </summary>
            public const string BasketLevelDiscounts = "BasketLevelDiscounts";

            /// <summary>
            /// The name of the ItemLevelDiscounts property for an item related to a LineItem.
            /// </summary>
            public const string ItemLevelDiscounts = "ItemLevelDiscounts";

            #endregion
        }

        #endregion
    }
}
